As part of my experiments with GTK4 and Haskell, I sought an application with a picture which would report on the co-ordinates if clicked on with a mouse. For my example image, I took the Flammarion wood engraving.
gtk-picture
With Stack, I created a new single-package project gtk-picture
with Main.hs
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
{-# LANGUAGE ImplicitParams #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Main ( main ) where import Control.Monad ( void ) import Data.GI.Base ( AttrOp (..), new, set ) import Data.Text ( Text ) import qualified Data.Text as T import qualified GI.Gtk as Gtk import Paths_gtk_picture ( getDataFileName ) activate :: Gtk.Application -> IO () activate app = do grid <- new Gtk.Grid [ #columnSpacing := 5 , #rowSpacing := 5 , #marginBottom := 5 , #marginEnd := 5 , #marginStart := 5 , #marginTop := 5 ] labelCoords <- mkLabel coordsIntro let onMouseClick :: Gtk.GestureClickPressedCallback onMouseClick _nPress x y = labelCoords `set` [ #label := coordsIntro <> showCoords x y ] gestureClick <- new Gtk.GestureClick [ On #pressed onMouseClick ] picture <- flammarionPicture picture `set` [ #canShrink := False ] #addController picture gestureClick #attach grid labelCoords 0 0 1 1 #attach grid picture 0 1 1 1 window <- new Gtk.ApplicationWindow [ #application := app , #title := "Clicking on a picture" , #child := grid ] window.show mkLabel :: Text -> IO Gtk.Label mkLabel label = new Gtk.Label [ #label := label , #halign := Gtk.AlignStart ] coordsIntro :: Text coordsIntro = "Mouse last clicked at: " showCoords :: Double -> Double -> Text showCoords x y = "(" <> x' <> ", " <> y' <> ")" where showCoord = T.show . (round :: Double -> Int) x' = showCoord x y' = showCoord y flammarionPicture :: IO Gtk.Picture flammarionPicture = Just <$> flammarionPngFile >>= Gtk.pictureNewForFilename flammarionPngFile :: IO FilePath flammarionPngFile = getDataFileName "Flammarion573x480.png" main :: IO () main = do app <- new Gtk.Application [ #applicationId := "com.pilgrem.gtk-picture" , On #activate (activate ?self) ] void $ app.run Nothing |
and package.yaml
(extract):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
spec-version: 0.36.0 name: gtk-picture version: 0.1.0.0 ... data-dir: resources data-files: - Flammarion573x480.png ... dependencies: - base >= 4.7 && < 5 - gi-gtk >= 4.0 - haskell-gi-base - text >= 2.1.2 ... executables: gtk-picture: main: Main.hs source-dirs: app generated-other-modules: Paths_gtk_picture ghc-options: - -threaded - -rtsopts - -with-rtsopts=-N |
and Stack project-level configuration:
1 2 3 4 5 6 7 8 9 10 |
snapshot: lts-22.41 # GHC 9.6.6 extra-deps: - Cabal-3.10.3.0 # As text is an extra-dep - Cabal-syntax-3.10.3.0 # As text is an extra-dep - gi-gdk-4.0.9 - gi-gsk-4.0.8 - gi-gtk-4.0.9 - parsec-3.1.17.0 # As text-2.1.2 is an extra-dep - text-2.1.2 # To use Data.Text.show |
GTK4
Including a picture was straightforward: there is a GTK4 widget Picture
, and an action to create one from a file is yielded by pictureNewForFilename
. I used a file Flammarion573x480.png
located in a directory resources
(as indicated by the package.yaml
extract). I also used a Paths_gtk_picture
module, autogenerated by Cabal
. In that regard, I used Hpack’s modern behaviour by specifying spec-version: 0.36.0
.
Handling the clicking on the picture also turned out to be straightforward. There is a GTK4 event controller GestureClick
, and an action to create one is provided by new
. The event controller provides a signal pressed
and a callback function can be connected to it. So, it was a matter of using #addController
to yield an action that added that event controller to the picture.
The result (stack exec -- gtk-picture
) was as below:
Packaging
stack build
reports that it has installed the executable in directory ...\gtk-picture.stack-work\install\5a333380\bin
. The structure of the 5a333380
directory (being the directory returned by command stack path --local-install-root
) includes:
1 2 3 4 5 6 7 8 9 |
bin └─gtk-picture.exe doc └─gtk-picture-0.1.0.0 └─LICENSE share └─x86_64-windows-ghc-9.6.6 └─gtk-picture-0.1.0.0 └─Flammarion573x480.png |
Executing gtk-picture
in the Stack environment puts certain MSYS2 directories on the PATH
. Executing it outside of that environment results in errors if those directories are not otherwise on the PATH
. For example:
The ldd
command, provided by MSYS2, can be used to identify the dynamic link libraries (DLL) on which gtk-picture.exe
depends:
1 2 3 4 5 6 7 8 9 10 11 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffd9ab50000) KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffd99260000) KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffd98220000) ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffd98610000) SHELL32.dll => /c/WINDOWS/System32/SHELL32.dll (0x7ffd997b0000) msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ffd97e80000) USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ffd98e60000) libgdk_pixbuf-2.0-0.dll => /mingw64/bin/libgdk_pixbuf-2.0-0.dll (0x7ffd4c8e0000) ... libsharpyuv-0.dll => /mingw64/bin/libsharpyuv-0.dll (0x7ffd3d580000) |
Each line output by ldd
begins with a tab character. The DLL located in /mingw64/bin
can be isolated using a regular expression:
1 2 3 4 5 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") | ForEach-Object {if ($_ -match '^\t(.*?) => /mingw.*$') {$matches[1]}} libgdk_pixbuf-2.0-0.dll ... libsharpyuv-0.dll |
Further, each of those isolated DLL can be copied to the same directory as the executable:
1 2 3 4 5 6 7 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") | ForEach-Object {if ($_ -Match '^\t(.*?) => /mingw.*$') {$matches[1]}} | ForEach-Object { Copy-Item (((stack path --programs) + "\msys2-20240727\mingw64\bin\")+$_) -Destination ((stack path --local-install-root) + "\bin") } |
Now, running the executable gtk-picture.exe
does not result in errors. However, running the executable from Windows also results in a terminal opening. This can be avoided by specifying the ghc-option
-optl-mwindows
, which specifies the option -mwindows
at the linking stage.